package hermodr

import (
	"io"
	"net/mail"
	"strings"

	"apiote.xyz/p/asgard/idavollr"
	"apiote.xyz/p/asgard/jotunheim"

	"apiote.xyz/p/gott/v2"
	"github.com/ProtonMail/gopenpgp/v2/helper"
	"github.com/emersion/go-imap"
	"github.com/emersion/go-smtp"
)

type EmptyMessageError struct{}

func (EmptyMessageError) Error() string {
	return "Server didn't return message body"
}

type HermodrMailbox struct {
	idavollr.Mailbox
}

type HermodrImapMessage struct {
	idavollr.ImapMessage
	config  jotunheim.Config
	literal imap.Literal
	mailMsg *mail.Message
	body    string
	plain   string
	armour  string
}

func redirectMessages(am idavollr.AbstractMailbox) error {
	m := am.(*HermodrMailbox)
	for msg := range m.Messages() {
		imapMessage := idavollr.ImapMessage{
			Msg:  msg,
			Sect: m.Section(),
		}
		imapMessage.SetClient(am.Client())
		r := gott.R[idavollr.AbstractImapMessage]{
			S: &HermodrImapMessage{
				ImapMessage: imapMessage,
				config:      m.Conf,
			},
		}.
			Bind(getBodySection).
			Bind(readLiteralMessage).
			Bind(readLiteralBody).
			Map(composePlaintextBody).
			Bind(encrypt).
			Tee(send).
			Tee(markRead).
			Tee(moveMessage)

		if r.E != nil {
			return r.E
		}
	}
	return nil
}

func getBodySection(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
	hm := m.(*HermodrImapMessage)
	r := hm.Message().GetBody(m.Section())
	if r == nil {
		return hm, EmptyMessageError{}
	}
	hm.literal = r
	return hm, nil
}

func readLiteralMessage(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
	hm := m.(*HermodrImapMessage)
	msg, err := mail.ReadMessage(hm.literal)
	hm.mailMsg = msg
	return hm, err
}

func readLiteralBody(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
	hm := m.(*HermodrImapMessage)
	body, err := io.ReadAll(hm.mailMsg.Body)
	hm.body = string(body)
	return hm, err
}

func composePlaintextBody(m idavollr.AbstractImapMessage) idavollr.AbstractImapMessage {
	hm := m.(*HermodrImapMessage)
	header := hm.mailMsg.Header
	plainText := "Content-Type: " + header.Get("Content-Type") + "; protected-headers=\"v1\"\r\n"
	plainText += "From: " + header.Get("From") + "\r\n"
	plainText += "Message-ID: " + header.Get("Message-ID") + "\r\n"
	plainText += "Subject: " + header.Get("Subject") + "\r\n"
	plainText += "\r\n"
	plainText += string(hm.body)
	hm.plain = plainText
	return hm
}

func encrypt(m idavollr.AbstractImapMessage) (idavollr.AbstractImapMessage, error) {
	hm := m.(*HermodrImapMessage)
	armour, err := helper.EncryptMessageArmored(hm.config.Hermodr.PublicKey, hm.plain)
	hm.armour = armour
	return hm, err
}

func send(m idavollr.AbstractImapMessage) error {
	hm := m.(*HermodrImapMessage)
	from := hm.mailMsg.Header.Get("From")
	date := hm.mailMsg.Header.Get("Date")
	messageID := hm.mailMsg.Header.Get("Message-ID")
	to := []string{hm.config.Hermodr.Recipient}
	msg := strings.NewReader("To: " + hm.config.Hermodr.Recipient + "\r\n" +
		"From: " + from + "\r\n" +
		"Date: " + date + "\r\n" +
		"Message-ID: " + messageID + "\r\n" +
		"MIME-Version: 1.0\r\n" +
		"Subject: ...\r\n" +
		"Content-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"---------------------997d365ae018229dc62ea2ff6b617cac\"; charset=utf-8\r\n" +
		"\r\n" +
		"This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n" +
		"-----------------------997d365ae018229dc62ea2ff6b617cac\r\n" +
		"Content-Type: application/pgp-encrypted\r\n" +
		"Content-Description: PGP/MIME version identification\r\n" +
		"\r\n" +
		"Version: 1\r\n" +
		"\r\n" +
		"-----------------------997d365ae018229dc62ea2ff6b617cac\r\n" +
		"Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n" +
		"Content-Description: OpenPGP encrypted message\r\n" +
		"Content-Disposition: inline; filename=\"encrypted.asc\"\r\n" +
		"\r\n" +
		hm.armour +
		"\r\n" +
		"\r\n" +
		"-----------------------997d365ae018229dc62ea2ff6b617cac--\r\n")
	err := smtp.SendMail(hm.config.Hermodr.SmtpServer, nil, hm.config.Hermodr.SmtpUsername, to, msg) // TODO send with login
	return err
}

func markRead(m idavollr.AbstractImapMessage) error {
	seqset := new(imap.SeqSet)
	seqset.AddNum(1) // TODO collect seqset
	item := imap.FormatFlagsOp(imap.AddFlags, true)
	flags := []interface{}{imap.SeenFlag}
	err := m.Client().Store(seqset, item, flags, nil) // TODO store outside of loop
	return err
}

func moveMessage(m idavollr.AbstractImapMessage) error { // TODO collect messages out of loop and move all
	hm := m.(*HermodrImapMessage)
	return idavollr.MoveMsg(m.Client(), hm.Msg, hm.config.Hermodr.ImapFolderRedirected)
}

func Hermodr(config jotunheim.Config) error {
	mailbox := &HermodrMailbox{
		Mailbox: idavollr.Mailbox{
			MboxName: config.Hermodr.ImapFolderInbox,
			ImapAdr:  config.Hermodr.ImapAddress,
			ImapUser: config.Hermodr.ImapUsername,
			ImapPass: config.Hermodr.ImapPassword,
			Conf:     config,
		},
	}

	mailbox.SetupChannels()
	r := gott.R[idavollr.AbstractMailbox]{
		S: mailbox,
	}.
		Bind(idavollr.Connect).
		Tee(idavollr.Login).
		Bind(idavollr.SelectInbox).
		Tee(idavollr.CheckEmptyBox).
		Map(idavollr.FetchMessages).
		Tee(redirectMessages).
		Recover(idavollr.IgnoreEmptyBox).
		Recover(idavollr.Disconnect)

	return r.E
}
